Customizing Extensions
======================
* scPantheon supports customizing extensions following certain formats.
* To insert your own method in scPantheon software, you can write a script in default extensions path:
* In Windows:
* In linux: ``.local/share/scpantheon/0.6.0.0/extensions``
* In MacOS:
Steps
-----
1. Create directory under the default path
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
:caption: eg: ``Clustering_with_Scanpy`` as your extensions name
mkdir Clustering_with_Scanpy
touch Clustering_with_Scanpy/module.py
2. In ``module.py`` import modules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
# import necessary modules
from bokeh.layouts import row, column
from scpantheon.widgets import Widgets
from scpantheon.buttons import Widget_type, make_widget
from bokeh.io import curdoc
import tabs as tb
import data as dt
# import other modules required here
import scanpy as sc
import base64
3. Create class ``Widget_Ext(Widget)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note:: Don't change the name of the class.
.. automodule:: Clustering_with_Scanpy.module
:members:
:undoc-members:
:show-inheritance:
``__init__``
************
.. code-block:: python
def __init__(self,
name: str | None = 'generic columns',
):
super().__init__(name)
self.init_extension()
super().init_tab()
.. note:: You don't need to change anything in this ``__init__`` function.
``init_extension``
******************
In this function, customize the initial tab in the visualization of your method by defining widgets and arranging it into a layout.
scPantheon is based on bokeh, however, you don't need to learn bokeh from scratch.
Instead, you can use some basic bokeh widgets we've encapsulated in :ref:`buttons` module.
First, create widgets with
.. py:function:: make_widget(widget_type: Widget_type, func = None, **kwargs)
:param widget_type: defines which kind of widget to create by format: ``Widget_type.type``
:param func: callback function if needed
:param \**kwargs: keyword arguments
.. note::
1. ``widget_type`` are limited and ``**kwargs`` are also limited according to widget_type. They are listed in the following table.
2. Widget_types like ``Widget_type.div`` that doesn't respond to callback functions are remarked as ``None`` in column **func**.
3. **necessary_param** lists parameters necessary to the widget_type. Without any one of them, the widget malfunctions.
4. **core_param** lists other useful parameters.
5. **all_param** lists all parameters allowed. Any parameters other than all_param will be automatically omitted by scPantheon.
.. list-table::
:header-rows: 1
* - widget_type
- func
- necessary_param
- core_param
- all_param
* - ``Widget_type.div``
- ``None``
- ``'text'``
- ``'disable_math'``
- .. line-block::
``'align'``, ``'aspect_ratio'``, ``'background'``, ``'css_classes'``, ``'default_size'``, ``'disable_math'``,\
``'disabled'``, ``'height'``, ``'height_policy'``, ``'js_event_callbacks'``, ``'js_property_callbacks'``, ``'margin'``,\
``'max_height'``, ``'max_width'``, ``'min_height'``, ``'min_width'``, ``'name'``, ``'render_as_text'``, ``'sizing_mode'``,\
``'style'``, ``'subscribed_events'``, ``'syncable'``, ``'tags'``, ``'text'``, ``'visible'``, ``'width'``, ``'width_policy'``
* - ``Widget_type.text``
- ``None``
- /
- ``'title'``, ``'value'``
- .. line-block::
``'align'``, ``'aspect_ratio'``, ``'background'``, ``'css_classes'``, ``'default_size'``, ``'disabled'``, ``'height'``,\
``'height_policy'``, ``'js_event_callbacks'``, ``'js_property_callbacks'``, ``'margin'``, ``'max_height'``, ``'max_length'``,\
``'max_width'``, ``'min_height'``, ``'min_width'``, ``'name'``, ``'placeholder'``, ``'sizing_mode'``, ``'subscribed_events'``,\
``'syncable'``, ``'tags'``, ``'title'``, ``'value'``, ``'value_input'``, ``'visible'``, ``'width'``, ``'width_policy'``\
* - ``Widget_type.button``
- allowed
- /
- ``'label'``
- .. line-block::
``'align'``, ``'aspect_ratio'``, ``'background'``, ``'button_type'``, ``'css_classes'``, ``'default_size'``,\
``'disabled'``, ``'height'``, ``'height_policy'``, ``'icon'``, ``'js_event_callbacks'``, ``'js_property_callbacks'``,\
``'label'``, ``'margin'``, ``'max_height'``, ``'max_width'``, ``'min_height'``, ``'min_width'``, ``'name'``,\
``'sizing_mode'``, ``'subscribed_events'``, ``'syncable'``, ``'tags'``, ``'visible'``, ``'width'``, ``'width_policy'``\
* - ``Widget_type.select``
- allowed
- ``'options'``, ``'value'``
- ``'title'``
- .. line-block::
``'align'``, ``'aspect_ratio'``, ``'background'``, ``'css_classes'``, ``'default_size'``, ``'disabled'``, ``'tags'``,\
``'height'``, ``'height_policy'``, ``'js_event_callbacks'``, ``'js_property_callbacks'``, ``'margin'``,\
``'max_height'``, ``'max_width'``, ``'min_height'``, ``'min_width'``, ``'name'``, ``'options'``, ``'sizing_mode'``,\
``'subscribed_events'``, ``'syncable'``, ``'title'``, ``'value'``, ``'visible'``, ``'width'``, ``'width_policy'``\
* - ``Widget_type.autocompleteInput``
- allowed
- ``'completions'``
- .. line-block::
``'min_characters'``, ``'value'``,\
``'case_sensitive'``, ``'title'``\
- .. line-block::
``'align'``, ``'aspect_ratio'``, ``'background'``, ``'case_sensitive'``, ``'completions'``, ``'css_classes'``,\
``'default_size'``, ``'disabled'``, ``'height'``, ``'height_policy'``, ``'js_event_callbacks'``,\
``'js_property_callbacks'``, ``'margin'``, ``'max_height'``, ``'max_length'``, ``'max_width'``, ``'min_characters'``,\
``'min_height'``, ``'min_width'``, ``'name'``, ``'placeholder'``, ``'restrict'``, ``'sizing_mode'``, ``'subscribed_events'``,\
``'syncable'``, ``'tags'``, ``'title'``, ``'value'``, ``'value_input'``, ``'visible'``, ``'width'``, ``'width_policy'``\
* - ``Widget_type.checkBoxGroup``
- allowed
- ``'labels'``
- ``'active'``
- .. line-block::
``'active'``, ``'align'``, ``'aspect_ratio'``, ``'background'``, ``'css_classes'``, ``'default_size'``, ``'disabled'``,\
``'height'``, ``'height_policy'``, ``'inline'``, ``'js_event_callbacks'``, ``'js_property_callbacks'``, ``'labels'``,\
``'margin'``, ``'max_height'``, ``'max_width'``, ``'min_height'``, ``'min_width'``, ``'name'``, ``'sizing_mode'``,\
``'subscribed_events'``, ``'syncable'``, ``'tags'``, ``'visible'``, ``'width'``, ``'width_policy'``\
* - ``Widget_type.radioButtonGroup``
- allowed
- ``'labels'``
- ``'active'``
- .. line-block::
``'active'``, ``'align'``, ``'aspect_ratio'``, ``'background'``, ``'button_type'``, ``'css_classes'``, ``'default_size'``,\
``'disabled'``, ``'height'``, ``'height_policy'``, ``'js_event_callbacks'``, ``'js_property_callbacks'``, ``'labels'``,\
``'margin'``, ``'max_height'``, ``'max_width'``, ``'min_height'``, ``'min_width'``, ``'name'``, ``'orientation'``,\
``'sizing_mode'``, ``'subscribed_events'``, ``'syncable'``, ``'tags'``, ``'visible'``, ``'width'``, ``'width_policy'``\
* - ``Widget_type.slider``
- allowed
- .. line-block::
``'start'``, ``'end'``,\
``'value'``, ``'step'``\
- .. line-block::
``'title'``, ``'format'``,\
``'orientation'``, \
``'show_value'``, ``'bar_color'``\
- .. line-block::
``'align'``, ``'aspect_ratio'``, ``'background'``, ``'bar_color'``, ``'css_classes'``, ``'default_size'``, ``'width_policy'``\
``'direction'``, ``'disabled'``, ``'end'``, ``'format'``, ``'height'``, ``'height_policy'``, ``'js_event_callbacks'``,\
``'js_property_callbacks'``, ``'margin'``, ``'max_height'``, ``'max_width'``, ``'min_height'``, ``'min_width'``,\
``'name'``, ``'orientation'``, ``'show_value'``, ``'sizing_mode'``, ``'start'``, ``'step'``, ``'subscribed_events'``,\
``'syncable'``, ``'tags'``, ``'title'``, ``'tooltips'``, ``'value'``, ``'value_throttled'``, ``'visible'``, ``'width'``,\
* - ``Widget_type.rangeSlider``
- allowed
- .. line-block::
``'start'``, ``'end'``,\
``'value'``, ``'step'``\
- .. line-block::
``'title'``, ``'format'``,\
``'orientation'``, \
``'show_value'``, ``'bar_color'``\
- .. line-block::
``'align'``, ``'aspect_ratio'``, ``'background'``, ``'bar_color'``, ``'css_classes'``, ``'default_size'``, ``'width_policy'``\
``'direction'``, ``'disabled'``, ``'end'``, ``'format'``, ``'height'``, ``'height_policy'``, ``'js_event_callbacks'``,\
``'js_property_callbacks'``, ``'margin'``, ``'max_height'``, ``'max_width'``, ``'min_height'``, ``'min_width'``,\
``'name'``, ``'orientation'``, ``'show_value'``, ``'sizing_mode'``, ``'start'``, ``'step'``, ``'subscribed_events'``,\
``'syncable'``, ``'tags'``, ``'title'``, ``'tooltips'``, ``'value'``, ``'value_throttled'``, ``'visible'``, ``'width'``,\
.. tip::
.. line-block::
To see detailed examples of all widget_types, please refer to :ref:`Widget_type`
To learn more about original bokeh widgets, please refer to `bokeh `_
.. code-block:: python
:caption: example in Clustering_with_Scanpy
def init_extension(self): # don't modify this
#customize you widgets
sc_cluster_step1_arg = {'label': 'Step1: Run PCA', 'button_type': 'success'}
sc_cluster_step1 = make_widget(Widget_type.button, lambda : self.pca(), **sc_cluster_step1_arg)
cl_input1 = make_widget(Widget_type.text, title = 'Neighbor Num:', value = '10')
cl_input2_arg = {'title':'Principal Component Num:', 'value': '40'}
cl_input2 = make_widget(Widget_type.text, **cl_input2_arg)
cl_input3_arg = {'title':'Resolution:', 'value': '1'}
cl_input3 = make_widget(Widget_type.text, **cl_input3_arg)
sc_cluster_step2_arg = {'label': 'Step2: Clustering with Neighborhood Graph', 'button_type': 'success'}
sc_cluster_step2 = make_widget(
Widget_type.button,
lambda: self.neighborhood_graph(cl_input1.value, cl_input2.value, cl_input3.value),
**sc_cluster_step2_arg
)
Then, add customized widgets into ``self.widgets_dict`` in ``init_extension``.
.. code-block:: python
widgets_dict = {
'sc_cluster_step1': sc_cluster_step1,
'cl_input1': cl_input1,
'cl_input2': cl_input2,
'cl_input3': cl_input3,
'sc_cluster_step2': sc_cluster_step2
}
self.widgets_dict = {**self.widgets_dict, **widgets_dict}
callback functions
******************
.. note::
1. Callback functions are designed to be asynchronous for safety. Don't modify the framework, but feel free to add parameters.
2. scPantheon supports global data ``dt.adata`` with ``dt.adata.obsm`` of type pandas Dataframe. You can change it to other data types if necessary.
3. Remember to change it back or format newly generated obsms back to pandas Dataframe type by ``dt.init_data(dt.adata, obsm_name)``.
4. Call ``super().update_tab(new_obsm, new_map, new_group)`` to update layout. This function also formats ``new_obsm`` back to pandas Dataframe type.
.. tip::
.. line-block::
For more information of parameters in ``dt.init_data``, please refer to :py:func:`~scpantheon.data.init_data`
For more information of parameters in ``update_tab``, please refer to :py:func:`~scpantheon.widgets.Widgets.update_tab`
.. code-block:: python
:caption: callback example 1: ``pca``
def pca(self):
tb.mute_global(tb.panel_dict, tb.curpanel, tb.ext_widgets)
def next_pca(self):
# Define callback function for customized widgets here.
# If more parameters are needed, add them in "def pca(self)" and "def next_pca(self)"
# Use scPantheon global data instance "dt.adata" for functions that require anndata inputs.
sc.tl.pca(dt.adata, svd_solver='arpack')
# A new map(coordinate system) 'X_pca' is generated in anndata.obsm.
# It's necessary to call dt.init_data with the key of the generated obsm ('X_pca').
# It formats the obsm into pd.Dataframe, which supports clustering operations in scPantheon.
dt.init_data(dt.adata, 'X_pca')
# If you want to display other widgets in callback functions, it's also feasible to add customed widgets here.
sc.pl.pca_variance_ratio(dt.adata, log=True)
img = open('figures/pca_variance_ratio.png','rb')
img_base64 = base64.b64encode(img.read()).decode("ascii")
pca_img = Div(text="
".format(img_base64))
widgets_dict = {'pca_img': pca_img}
self.widgets_dict = {**self.widgets_dict, **widgets_dict}
# Update visualization. Only change the parameters.
super().update_tab(new_map = 'X_pca' )
tb.unmute_global(tb.panel_dict, tb.curpanel, tb.ext_widgets)
curdoc().add_next_tick_callback(lambda: next_pca(self))
.. code-block:: python
:caption: callback example 2: ``neighborhood_graph``
def neighborhood_graph(self, neighbor_num, pc_num, resolution):
tb.mute_global(tb.panel_dict, tb.curpanel, tb.ext_widgets) # don't modify this
def next_neighborhood_graph(self, neighbor_num, pc_num, resolution):
# The type of dt.adata.obsm is pd.Dataframe by default.
# Format dt.adata.obsm if necessary in following operations
dt.adata.obsm['X_pca'] = dt.adata.obsm['X_pca'].to_numpy()
# main operations in callback function
sc.pp.neighbors(dt.adata, n_neighbors=int(neighbor_num), n_pcs=int(pc_num))
sc.tl.umap(dt.adata)
sc.tl.leiden(dt.adata, resolution=float(resolution), flavor="igraph", n_iterations=2, directed=False)
# Update visualization. Only change the parameters.
super().update_tab(new_obsm = 'X_umap', new_map = 'X_umap', new_group = 'leiden')
tb.unmute_global(tb.panel_dict, tb.curpanel, tb.ext_widgets) # Don't modify this
curdoc().add_next_tick_callback(lambda: next_neighborhood_graph(self, neighbor_num, pc_num, resolution))
create layout
*************
.. code-block:: python
def update_layout(self):
super().update_layout() # don't modify this
# column(list) arrange widgets or layouts in a column. row(list) arrange widgets or layouts in a row.
# Customize your own layout by calling the keys in self.widgets_dict
sccluster_key = ['sc_cluster_step1', 'cl_input1', 'cl_input2', 'cl_input3', 'sc_cluster_step2']
values = [self.widgets_dict[key] for key in sccluster_key if key in self.widgets_dict]
layout_sccluster = column(values)
pca_img_key = ['pca_img']
values = [self.widgets_dict[key] for key in pca_img_key if key in self.widgets_dict]
layout_pca_img = column(values)
# Merge it with basic layout with format self.layout = column([self.layout, _____ ])
self.layout = column([self.layout, row([layout_sccluster, layout_pca_img])])